package control.character;

import com.jme3.animation.AnimChannel;
import com.jme3.animation.AnimControl;
import com.jme3.animation.AnimEventListener;
import com.jme3.animation.LoopMode;
import com.jme3.bullet.collision.PhysicsCollisionObject;
import com.jme3.bullet.collision.shapes.CollisionShape;
import com.jme3.bullet.control.CharacterControl;
import com.jme3.bullet.control.GhostControl;
import com.jme3.bullet.control.GhostControl;
import com.jme3.bullet.control.RigidBodyControl;
import com.jme3.bullet.objects.PhysicsCharacter;
import com.jme3.math.FastMath;
import com.jme3.math.Vector3f;
import com.jme3.renderer.Camera;
import com.jme3.scene.Spatial;
import com.jme3.scene.control.CameraControl;
import control.object.HoldControl;
import game.constant.Constants;
import game.gameobject.character.CharacterSettings;

public class MyCharacterControl extends CharacterControl implements AnimEventListener {
    GhostControl ghostControl;

    public MyCharacterControl(CollisionShape shape, float stepHeight) {
	super(shape, stepHeight);
	ghostControl = new GhostControl(shape);
    }

    @Override
    public void update(float tpf) {
	checkForCharacterCollisions();
	super.update(tpf);
	checkHeldObject();
	updateWalkAnimation();
	updateView();
    }

    protected void checkForCharacterCollisions() {
	for(PhysicsCollisionObject collision : ghostControl.getOverlappingObjects()) {
	    Spatial collidingSpatial = (Spatial)collision.getUserObject();
	    if(!collidingSpatial.equals(spatial)) {
		if(collidingSpatial.getControl(MyCharacterControl.class) != null)
		    collideCharacters(collidingSpatial);
	    }
	}
    }

    public void collideCharacters(Spatial collidingCharacter) {
	//separate spatials if they're both characters
	CharacterControl charControl = collidingCharacter.getControl(CharacterControl.class);
	//TODO separate colliding characters using mathemagic
	charControl.jump();
    }

    public void updateWalkAnimation() {
	AnimControl animControl = spatial.getControl(AnimControl.class);
	if(animControl != null) {
	    AnimChannel walkChannel = animControl.getChannel(Constants.CHARACTER_ANIM_CHANNEL_WALK);
	    if(!walkDirection.equals(Vector3f.ZERO)) {
		if(!walkChannel.getAnimationName().equals(Constants.CHARACTER_ANIM_WALK)) {
		    walkChannel.setAnim(Constants.CHARACTER_ANIM_WALK, 0.5f);
		}
	    } else {
		walkChannel.setAnim(Constants.CHARACTER_ANIM_STAND, 0.5f);
	    }

	    //this is here just for testing of multiple channels
	    AnimChannel rightArmChannel = animControl.getChannel(Constants.CHARACTER_ANIM_CHANNEL_RIGHT_ARM);
	    if(!rightArmChannel.getAnimationName().equals(Constants.CHARACTER_ANIM_ATTACK_RIGHT)) {
		rightArmChannel.setAnim(Constants.CHARACTER_ANIM_ATTACK_RIGHT, 0.5f);
		rightArmChannel.setLoopMode(LoopMode.DontLoop);
	    }
	    AnimChannel leftArmChannel = animControl.getChannel(Constants.CHARACTER_ANIM_CHANNEL_LEFT_ARM);
	    if(!leftArmChannel.getAnimationName().equals(Constants.CHARACTER_ANIM_ATTACK_LEFT)) {
		leftArmChannel.setAnim(Constants.CHARACTER_ANIM_ATTACK_LEFT, 0.5f);
		leftArmChannel.setLoopMode(LoopMode.DontLoop);
	    }
	}
    }

    /** Update the view direction of the character. */
    protected void updateView() {
	Camera cam = spatial.getControl(CameraControl.class).getCamera();
	cam.setLocation(getPhysicsLocation());
        setViewDirection(cam.getDirection());
    }

    protected void checkHeldObject() {
	if(isHoldingObject()) {
	    //TODO check to see if the held object should be dropped based on... some... conditions
	    //mainly it being too far away from the character, say, because it got stuck on a wall

	    Spatial heldObject = spatial.getUserData(Constants.CHARACTER_HELD_OBJECT);
	    RigidBodyControl control = heldObject.getControl(RigidBodyControl.class);
            float charMass = ((CharacterSettings)spatial.getUserData(Constants.CHARACTER_SETTINGS_OBJ)).getMass();
            float slowingFactor = 1/FastMath.sqrt((charMass+control.getMass())/charMass);
            setWalkDirection(walkDirection.multLocal(slowingFactor));
	}
    }

    public boolean isHoldingObject() {
	return spatial.getUserData(Constants.CHARACTER_HELD_OBJECT) instanceof Spatial;
    }

    public void setHeldObject(Spatial objectToHold) {
	if(objectToHold != null) {
	    //TODO set appropriate rotation on picked-up objects
	    if(isHoldingObject()) {
		Spatial alreadyHeldObject = spatial.getUserData(Constants.CHARACTER_HELD_OBJECT);
		if(!alreadyHeldObject.equals(objectToHold)) {
		    dropHeldObject();
		    objectToHold.addControl(new HoldControl(this));
		    spatial.setUserData(Constants.CHARACTER_HELD_OBJECT, objectToHold);
		}
	    } else {
		objectToHold.addControl(new HoldControl(this));
		spatial.setUserData(Constants.CHARACTER_HELD_OBJECT, objectToHold);
	    }
	} else {
	    dropHeldObject();
	}
    }

    public void dropHeldObject() {
	if(spatial.getUserData(Constants.CHARACTER_HELD_OBJECT) instanceof Spatial) {
	    Spatial heldObject = spatial.getUserData(Constants.CHARACTER_HELD_OBJECT);
	    heldObject.removeControl(HoldControl.class);
	}

	spatial.setUserData(Constants.CHARACTER_HELD_OBJECT, "dummyValue"); //userData doesn't allow null, so just set it to something that's not a spatial
    }

    public Vector3f getFeetWorldTranslation() {
	Vector3f feet = getPhysicsLocation().clone();
	CharacterSettings charSettings = spatial.getUserData(Constants.CHARACTER_SETTINGS_OBJ);
	feet.setY(feet.getY() - charSettings.getHeight()/2);
	return feet;
    }

    public void sprint(boolean isSprinting) {
	if(isSprinting)
	    spatial.setUserData(Constants.CHARACTER_CURRENT_SPEED, ((CharacterSettings)spatial.getUserData(Constants.CHARACTER_SETTINGS_OBJ)).getSprintSpeed());
	else
	    spatial.setUserData(Constants.CHARACTER_CURRENT_SPEED, ((CharacterSettings)spatial.getUserData(Constants.CHARACTER_SETTINGS_OBJ)).getWalkSpeed());
    }

    public void onAnimCycleDone(AnimControl control, AnimChannel channel, String animName) {
	if(channel.getLoopMode() == LoopMode.DontLoop) {
	    channel.setAnim(Constants.CHARACTER_ANIM_STAND, 0.5f);
	}
    }

    public void onAnimChange(AnimControl control, AnimChannel channel, String animName) {}

    @Override
    public void setSpatial(Spatial spatial) {
	super.setSpatial(spatial);
	spatial.addControl(ghostControl);
    }

    public GhostControl getGhostControl() {
	return ghostControl;
    }
}
